package kr.ac.ssu.ailab.Control;

import java.awt.Point;
import java.util.Vector;

import kr.ac.ssu.ailab.Model.Data.Cell;
import kr.ac.ssu.ailab.View.SeqTest;

/**
 * 이 클래스는 기본 astar 알고리즘을 이용해 Cluster내의 CSC cell 들의 연결여부를 반영한다. 시각적인 표현이 필요없는
 * 부분이기 때문에 thread는 사용하지 않으며 결과값은 boolean 으로 return 된다.
 * 
 * @author Neil
 * */
public class SearchModule
{
	public final int TEST_TIME = 10;

	public final int NO_PATH = -1;
	public final int NOT_FOUND = 0;
	public final int FOUND = 1;
	public final int FINISH_STATE = 2;
	public final int SHOW = 3;

	private Vector<Cell> openList = null;
	private Vector<Cell> closedList = null;

	private Cell finish = null;
	private Cell first = null;

	public int openListCount = 0; // for Test\
	public int closedListCount = 0; // for Test\
	private Runtime runtime = Runtime.getRuntime();

	public double searchSpeedStart = 0;
	public double searchSpeedEnd = 0;

	private boolean result = false;
	private double result_score = 0;

	/**
	 * Constructor of Class SearchModule 부분적인 searching의 start 와 goal 위치를 설정한다.
	 * 
	 * @param start
	 * @param finish
	 */
	public SearchModule(Cell start, Cell finish)
	{
		this.openList = null;
		this.closedList = null;
		this.first = start;
		this.finish = finish;

		this.closedListCount = 0;
		this.openListCount = 0;
		this.searchSpeedStart = 0;
		this.searchSpeedEnd = 0;
		this.result = false;
		this.result_score = 0;
	}

	/**
	 * Instance 생성시 설정된 start와 goal 위치를 기반으로 search 를 시작한다
	 */
	public void startSearch()
	{

		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();
		searchSpeedStart = System.currentTimeMillis();

		int state = NOT_FOUND;
		openList.addElement(first);

		while (state == NOT_FOUND)
		{
			switch (SeqTest.mapControl.getLevel())
				{
				case 1:
					state = step_Level1();
					break;
				case 2:
				case 3:
				case 4:
				case 5:
					state = step_Level2();
					break;

				default:
					System.err.println("잘못된 레벨 입력값 입니다.");
					break;

				}

		}
		/* Show optimized path if being found */
		if (state == FOUND)
		{
		}
		else
		{
			// System.out.println("No Path Found");
		}
		// System.out.println(" Search Count : " + openListCount);

	}

	/**
	 * 최종적인 low level 의 start 와 goal cell 을 graph node에 추가한다.
	 * 
	 * @return
	 */
	public boolean StartFinishAddSearch()
	{
		openList = new Vector<Cell>();
		closedList = new Vector<Cell>();

		int state = NOT_FOUND;
		openList.addElement(first);

		while (state == NOT_FOUND)
		{
			switch (SeqTest.mapControl.getLevel())
				{
				case 1:
					state = stepSF_Level1();
					break;
				case 2:
				case 3:
				case 4:
				case 5:
					state = stepSF_Level2();
					break;
				default:
					System.err.println("잘못된 레벨 번호 입니다.");
				}

		}
		/* Show optimized path if being found */
		if (state == FOUND)
		{
			return true;
		}
		else
		{
			System.out.println("No Path Found (Search Module)");
			return false;
		}
	}

	/**
	 * 난이도 조절에 따른 Level1 시작과 끝을 연결하는 Graph 구성
	 * 
	 * @return int
	 * */
	public int stepSF_Level1()
	{
		int tests = 0;
		boolean found = false;

		Vector<Cell> temp = (Vector<Cell>) openList.clone();
		// find the most promising edge cell
		double min = Double.MAX_VALUE;
		double score = 0;

		// int best = -1;
		Cell best = (Cell) temp.elementAt(temp.size() - 1);
		Cell now;

		for (int i = 0; i < temp.size(); i++)
		{
			now = (Cell) temp.elementAt(i);
			if (!closedList.contains(now))
			{
				score = now.getDistFromStart();
				score += now.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = now;
				}
			}
		}
		now = best;

		openList.removeElement(now);
		closedList.addElement(now);
		closedListCount++;

		Cell next[] = SeqTest.mapControl.getAdjacentCell(now);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				if (!next[i].isObstacle())
				{
					double cost = 1.0 + now.getDistFromStart();
					next[i].setDistFromStart(cost);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));

					if (!openList.contains(next[i])
							&& !closedList.contains(next[i])
							&& (next[i].getIsIncludedIn() == first.getIsIncludedIn() || next[i]
									.getIsIncludedIn() == finish.getIsIncludedIn()))
					{
						openList.addElement(next[i]);
						openListCount++;

					}
				}
			}
			if (found)
			{
				setResult_score(score);
				for (int k = 0; k < openList.size(); k++)
				{
					openList.get(k).setDistFromFinish(0);
					openList.get(k).setDistFromStart(0);
				}
				for (int k = 0; k < closedList.size(); k++)
				{
					closedList.get(k).setDistFromFinish(0);
					closedList.get(k).setDistFromStart(0);
				}
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}
		// now process best.
		return NOT_FOUND;
	}

	/**
	 * 난이도 조절에 따른 Level2 시작과 끝을 연결하는 Graph 구성
	 * 
	 * @return int
	 * */
	public int stepSF_Level2()
	{
		int tests = 0;
		boolean found = false;

		Vector<Cell> temp = (Vector<Cell>) openList.clone();
		// find the most promising edge cell
		double min = Double.MAX_VALUE;
		double score = 0;

		// int best = -1;
		Cell best = (Cell) temp.elementAt(temp.size() - 1);
		Cell now;

		for (int i = 0; i < temp.size(); i++)
		{
			now = (Cell) temp.elementAt(i);
			if (!closedList.contains(now))
			{
				score = now.getDistFromStart();
				score += now.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = now;
				}
			}
		}
		now = best;

		openList.removeElement(now);
		closedList.addElement(now);
		closedListCount++;

		Cell next[] = SeqTest.mapControl.getAdjacentCell(now);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				if (!next[i].isObstacle())
				{
					double cost = next[i].getWeight() + now.getDistFromStart();
					next[i].setDistFromStart(cost);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));

					if (!openList.contains(next[i])
							&& !closedList.contains(next[i])
							&& (next[i].getIsIncludedIn() == first.getIsIncludedIn() || next[i]
									.getIsIncludedIn() == finish.getIsIncludedIn()))
					{
						openList.addElement(next[i]);
						openListCount++;

					}
				}
			}
			if (found)
			{
				setResult_score(score);
				for (int k = 0; k < openList.size(); k++)
				{
					openList.get(k).setDistFromFinish(0);
					openList.get(k).setDistFromStart(0);
				}
				for (int k = 0; k < closedList.size(); k++)
				{
					closedList.get(k).setDistFromFinish(0);
					closedList.get(k).setDistFromStart(0);
				}
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}
		// now process best.
		return NOT_FOUND;
	}

	/**
	 * 난이도 조절에 따른 Level1 Graph 구성
	 * 
	 * @return 그래프 구성 결과
	 */
	public int step_Level1()
	{
		int tests = 0;
		boolean found = false;

		Vector<Cell> temp = (Vector<Cell>) openList.clone();
		// find the most promising edge cell
		double min = Double.MAX_VALUE;
		double score = 0;

		// int best = -1;
		Cell best = (Cell) temp.elementAt(temp.size() - 1);
		Cell now;

		for (int i = 0; i < temp.size(); i++)
		{
			now = (Cell) temp.elementAt(i);
			if (!closedList.contains(now))
			{
				score = now.getDistFromStart();
				score += now.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = now;
				}
			}
		}
		now = best;

		openList.removeElement(now);
		closedList.addElement(now);

		Cell next[] = SeqTest.mapControl.getAdjacentCell(now);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				if (!next[i].isObstacle())
				{
					double cost = 1.0 + now.getDistFromStart();
					next[i].setDistFromStart(cost);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));
					if (!openList.contains(next[i])
							&& !closedList.contains(next[i])
							&& (next[i].getIsIncludedIn() == first.getIsIncludedIn() || next[i]
									.getIsIncludedIn() == finish.getIsIncludedIn()))

					{
						openList.addElement(next[i]);
						openListCount++;

					}
				}
			}
			if (found)
			{

				setResult_score(score);
				setResult(true);

				for (int k = 0; k < openList.size(); k++)
				{
					openList.get(k).setDistFromFinish(0);
					openList.get(k).setDistFromStart(0);
				}
				for (int k = 0; k < closedList.size(); k++)
				{
					closedList.get(k).setDistFromFinish(0);
					closedList.get(k).setDistFromStart(0);
				}
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}
		// now process best.
		return NOT_FOUND;
	}

	/**
	 * 난이도 조절에 따른 Level2 Graph 구성
	 * 
	 * @return 그래프 구성 결과
	 */
	public int step_Level2()
	{
		int tests = 0;
		boolean found = false;

		Vector<Cell> temp = (Vector<Cell>) openList.clone();
		// find the most promising edge cell
		double min = Double.MAX_VALUE;
		double score = 0;

		// int best = -1;
		Cell best = (Cell) temp.elementAt(temp.size() - 1);
		Cell now;

		for (int i = 0; i < temp.size(); i++)
		{
			now = (Cell) temp.elementAt(i);
			if (!closedList.contains(now))
			{
				score = now.getDistFromStart();
				score += now.getDistFromFinish();

				if (score < min)
				{
					min = score;
					best = now;
				}
			}
		}
		now = best;

		openList.removeElement(now);
		closedList.addElement(now);
		closedListCount++;

		Cell next[] = SeqTest.mapControl.getAdjacentCell(now);
		int length = next.length;
		for (int i = 0; i < length; i++)
		{
			if (next[i] != null)
			{
				if (next[i] == finish)
				{
					found = true;
				}
				if (!next[i].isObstacle())
				{
					double cost = next[i].getWeight() + now.getDistFromStart();
					next[i].setDistFromStart(cost);
					next[i].setDistFromFinish(tie_breaking_Heuristic(next[i], finish, 1));
					if (!openList.contains(next[i])
							&& !closedList.contains(next[i])
							&& (next[i].getIsIncludedIn() == first.getIsIncludedIn() || next[i]
									.getIsIncludedIn() == finish.getIsIncludedIn()))

					{
						openList.addElement(next[i]);
						openListCount++;

					}
				}
			}
			if (found)
			{

				setResult_score(score);
				setResult(true);

				for (int k = 0; k < openList.size(); k++)
				{
					openList.get(k).setDistFromFinish(0);
					openList.get(k).setDistFromStart(0);
				}
				for (int k = 0; k < closedList.size(); k++)
				{
					closedList.get(k).setDistFromFinish(0);
					closedList.get(k).setDistFromStart(0);
				}
				return FOUND;
			}
		}

		if (openList.size() == 0)
		{
			return NO_PATH;
		}
		// now process best.
		return NOT_FOUND;
	}

	/**
	 * calculates the Cross Product Distance from a to b
	 */
	public double tie_breaking_Heuristic(Cell nodeA, Cell nodeB, double low)
	{
		int nodeAX = nodeA.getPosition().x;
		int nodeAY = nodeA.getPosition().y;
		int nodeBX = nodeB.getPosition().x;
		int nodeBY = nodeB.getPosition().y;
		Point start = first.getPosition();

		double dx1 = nodeAX - nodeBX;
		double dy1 = nodeAY - nodeBY;
		double dx2 = start.x - nodeBX;
		double dy2 = start.y - nodeBY;
		double cross = dx1 * dy2 - dx2 * dy1;
		if (cross < 0)
			cross = -cross; // absolute value

		/**/
		return (low * (Math.abs(dx1) + Math.abs(dy1) + cross * 0.0002)) * 2;

	}

	/**
	 * 결과가 제대로 생성되었는지 유무를 set 하는 메소드
	 * 
	 * @param result
	 */
	public void setResult(boolean result)
	{
		this.result = result;
	}

	/**
	 * 결과가 제대로 생성되었는지 유무를 확인 하는 메소드
	 * 
	 * @return
	 */
	public boolean isResult()
	{
		return result;
	}

	/**
	 * 결과의 Result_Score 를 set 하는 메소드
	 * 
	 * @param result_score
	 */
	public void setResult_score(double result_score)
	{
		this.result_score = result_score;
	}

	/**
	 * 결과의 Result_Score 를 get 하는 메소드
	 * 
	 * @return
	 */
	public double getResult_score()
	{
		return result_score;
	}

}
